﻿using System;
using USNetFramework.Utils;

namespace Crypt.Skynet
{
    // 通用加密
    public static class Crypt
    {
        // The biggest 64bit prime
        private const ulong P = 0xffffffffffffffc5ul;
        private const ulong G = 5ul;


        // Constants are the integer part of the sines of integers (in radians) * 2^32.
        private static uint[] k = {
        0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee ,
        0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501 ,
        0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be ,
        0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821 ,
        0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa ,
        0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8 ,
        0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed ,
        0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a ,
        0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c ,
        0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70 ,
        0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05 ,
        0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665 ,
        0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039 ,
        0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1 ,
        0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1 ,
        0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 };

        // r specifies the per-round shift amounts
        private static uint[] r = {
        7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22,
        5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20, 5,  9, 14, 20,
        4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
        6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21};


        // 生成秘钥
        private static ulong _mul_mod_p(ulong a, ulong b)
        {
            ulong m = 0;
            while (b > 0)
            {
                if ((b&1) > 0)
                {
                    ulong t = P - a;
                    if (m >= t)
                    {
                        m -= t;
                    }
                    else
                    {
                        m += a;
                    }
                }
                if (a >= P - a)
                {
                    a = a * 2 - P;
                }
                else
                {
                    a = a * 2;
                }
                b >>= 1;
            }
            return m;
        }

        private static ulong _pow_mod_p(ulong a, ulong b)
        {
            if (b == 1)
            {
                return a;
            }
            ulong t = _pow_mod_p(a, b >> 1);
            t = _mul_mod_p(t, t);
            if (1 == b%2)
            {
                t = _mul_mod_p(t, a);
            }
            return t;
        }

        private static ulong _powmodp(ulong a, ulong b)
        {
            if (a > P)
                a %= P;
            return _pow_mod_p(a, b);
        }

        private static uint _left_rotate(uint x, int c)
        {
            return (((x) << (c)) | ((x) >> (32 - (c))));
        }

        private static void _digest_md5(uint[] w, ref uint[] rt)
        {
            uint a, b, c, d, f, g, temp;
            uint i;

            a = 0x67452301u;
            b = 0xefcdab89u;
            c = 0x98badcfeu;
            d = 0x10325476u;

            for (i = 0; i < 64; i++)
            {
                if (i < 16)
                {
                    f = (b & c) | ((~b) & d);
                    g = i;
                }
                else if (i < 32)
                {
                    f = (d & b) | ((~d) & c);
                    g = (5 * i + 1) % 16;
                }
                else if (i < 48)
                {
                    f = b ^ c ^ d;
                    g = (3 * i + 5) % 16;
                }
                else
                {
                    f = c ^ (b | (~d));
                    g = (7 * i) % 16;
                }

                temp = d;
                d = c;
                c = b;
                b = b + _left_rotate(a + f + k[i] + w[g], (int)r[i]);
                a = temp;
            }

            rt[0] = a;
            rt[1] = b;
            rt[2] = c;
            rt[3] = d;
        }

        private static byte[] _hmac(byte[] inX, byte[] inY)
        {
            uint[] w = new uint[16];
            uint[] rt = new uint[4];
            int i;

            uint x1 = (uint)inX[0] | (uint)inX[1] << 8 | (uint)inX[2] << 16 | (uint)inX[3] << 24;
            uint x2 = (uint)inX[4] | (uint)inX[5] << 8 | (uint)inX[6] << 16 | (uint)inX[7] << 24;
            uint y1 = (uint)inY[0] | (uint)inY[1] << 8 | (uint)inY[2] << 16 | (uint)inY[3] << 24;
            uint y2 = (uint)inY[4] | (uint)inY[5] << 8 | (uint)inY[6] << 16 | (uint)inY[7] << 24;

            for (i = 0; i < 16; i += 4)
            {
                w[i] = x2;
                w[i + 1] = x1;
                w[i + 2] = y2;
                w[i + 3] = y1;
            }

            _digest_md5(w, ref rt);
            uint[] result = new uint[2];
            result[0] = rt[2] ^ rt[3];
            result[1] = rt[0] ^ rt[1];

            byte[] tmp = new byte[8];
            tmp[0] = (byte)(result[0] & 0xff);
            tmp[1] = (byte)((result[0] >> 8) & 0xff);
            tmp[2] = (byte)((result[0] >> 16) & 0xff);
            tmp[3] = (byte)((result[0] >> 24) & 0xff);
            tmp[4] = (byte)(result[1] & 0xff);
            tmp[5] = (byte)((result[1] >> 8) & 0xff);
            tmp[6] = (byte)((result[1] >> 16) & 0xff);
            tmp[7] = (byte)((result[1] >> 24) & 0xff);
            return tmp;
        }

        private static byte[] _hash(byte[] inBytes, int inSz) {
	        uint djb_hash = 5381;
            uint js_hash = 1315423911;

            byte[] result = new byte[8];

            int i;
	        for (i=0; i<inSz; i++) {
		        byte c = inBytes[i];
                djb_hash += (djb_hash << 5) + c;
		        js_hash ^= ((js_hash << 5) + c + (js_hash >> 2));
	        }

            result[0] = (byte)((djb_hash      ) & 0xff);
            result[1] = (byte)((djb_hash >>  8) & 0xff);
            result[2] = (byte)((djb_hash >> 16) & 0xff);
            result[3] = (byte)((djb_hash >> 24) & 0xff);

            result[4] = (byte)((js_hash      ) & 0xff);
            result[5] = (byte)((js_hash >>  8) & 0xff);
            result[6] = (byte)((js_hash >> 16) & 0xff);
            result[7] = (byte)((js_hash >> 24) & 0xff);

            return result;
        }

        // 随机key
        public static byte[] RandomKey()
        {
            byte[] tmp = new byte[8];
            int i;
            byte x = 0;
            Random rd = new Random();
            for (i = 0; i < 8; i++)
            {
                tmp[i] = (byte)(rd.Next() & 0xff);
                x ^= tmp[i];
            }
            if (x == 0)
            {
                tmp[0] |= 1;    // avoid 0
            }
            return tmp;
        }

        public static byte[] DHSecret(byte[] inX, byte[] inY)
        {
            if (8 != inX.Length || 8 != inY.Length)
            {
                Log.WriteInfo("Invalid dh uint64 key");
                return null;
            }

            ulong x = (ulong)inX[0] | (ulong)inX[1] << 8 | (ulong)inX[2] << 16 | (ulong)inX[3] << 24 | (ulong)inX[4] << 32 | (ulong)inX[5] << 40 | (ulong)inX[6] << 48 | (ulong)inX[7] << 56;
            ulong y = (ulong)inY[0] | (ulong)inY[1] << 8 | (ulong)inY[2] << 16 | (ulong)inY[3] << 24 | (ulong)inY[4] << 32 | (ulong)inY[5] << 40 | (ulong)inY[6] << 48 | (ulong)inY[7] << 56;

            ulong r = _powmodp(x, y);

            return BitConverter.GetBytes(r);
        }

        public static byte[] DHExchange(byte[] inBytes)
        {
            if (8 != inBytes.Length)
            {
                Log.WriteInfo("Invalid dh uint64 key");
                return null;
            }

            ulong x64 = (ulong)inBytes[0] | (ulong)inBytes[1] << 8 | (ulong)inBytes[2] << 16 | (ulong)inBytes[3] << 24 | (ulong)inBytes[4] << 32 | (ulong)inBytes[5] << 40 | (ulong)inBytes[6] << 48 | (ulong)inBytes[7] << 56;

            if (0 == x64)
            {
                Log.WriteInfo("Can't be 0");
                return null;
            }

            ulong r = _powmodp(G, x64);
            return BitConverter.GetBytes(r);
        }

        public static byte[] HMac64(byte[] inX, byte[] inY)
        {
            return _hmac(inX, inY);
        }

        public static byte[] HashKey(byte[] inBytes)
        { 
            return _hash(inBytes, inBytes.Length);
        }
    }
        
}
